#include <pthread.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <errno.h>

#include "curl/curl.h"
#include "libcjson/cJSON.h"
#include "libsha1/libsha1.h"

#include "aliyun_iot_mqtt_client.h"
#include "aliyun_iot_auth.h"
#include "aliiot.h"
#include "shm_data.h"


#define HTTP_AUTH_URL               "http://114.55.114.197/parkingFee/device/bindDevice.shtml"

#define HTTP_AUTH_APPKEY            "d3c5807175b7453e85da77cb66e11a09"
#define HTTP_AUTH_APPSECRET         "9001f2adkda2e5dd7"

#define HTTP_RESULT_MAX_SIZE        1000
#define HTTP_WRITEDATA_FILENAME     "/home/admin/aliiot_http.log"

static opendev_socket_t         opendev_socket;
static aliyun_iot_auth_param_t  aliyun_iot_auth_param;
static share_data_t *pshm;

static int file_read(const char *filename, char *buf, int buflen)
{
    FILE *fp;
    int flen;

    // get file length
    fp = fopen(filename, "r");
    if (fp)
    {
        fseek(fp, 0L, SEEK_END); 
        flen = ftell(fp);
        if (flen > buflen)
        {
            flen = buflen;
        }

        // read all
        fseek(fp, 0L, SEEK_SET);
        fread(buf, flen, 1, fp); 
        buf[flen] = 0; // file end

        fclose(fp);

        return 0;
    }

    return -1;
} 

/********************************************************
 * gen random string
 *******************************************************/
void genRandomString(char *str, int length)
{  
    int flag, i;  

    srand((unsigned)time(NULL));  
    for (i = 0; i < length; i++)  
    {   
        flag = rand() % 2;
        switch (flag)  
        {
            case 0:  
                str[i] = 'a' + rand() % 26;  
                break;  
            case 1:  
                str[i] = '0' + rand() % 10;  
                break;  
            default:
                str[i] = 'x';  
                break;  
        }
    }   
    str[length] = '\0';  
}

/********************************************************
 *            接收消息的回调函数
 *
 * 说明：当其它设备的消息到达时，此函数将被执行。
 ********************************************************/
static void messageArrived(MessageData *md)
{
    //接收消息缓存
    char msg[MSG_LEN_MAX] = {0};
    opendev_socket_t *psocket = &opendev_socket;

    MQTTMessage *message = md->message;
    if(message->payloadlen > MSG_LEN_MAX - 1)
    {
        printf("process part of receive message\n");
        message->payloadlen = MSG_LEN_MAX - 1;
    }

    //复制接收消息到本地缓存
    memcpy(msg, message->payload, message->payloadlen);

    //打印接收消息
    printf("Message : %s\n", msg);

    //to-do此处可以增加用户自己业务逻辑，例如：开关灯等操作
    if (psocket->isready)
    {
        write(psocket->cli_sockfd, msg, message->payloadlen);
    }
}

static int messageSend(Client *client, const char *buf)
{
    MQTTMessage message;
    static unsigned short message_id = 0;
    int rc = 0;

    memset(&message,0x0,sizeof(message));

    message.qos = QOS1;
    message.retained = FALSE_IOT;
    message.dup = FALSE_IOT;
    message.payload = (void *) buf;
    message.payloadlen = strlen(buf);
    message.id = ++message_id;

    rc = aliyun_iot_mqtt_publish(client, aliyun_iot_auth_param.topic1, &message);
    if (SUCCESS_RETURN != rc)
    {
        printf("ali_iot_mqtt_publish failed ret = %d\n", rc);
    }
    else
    {
        printf("ali_iot_mqtt_publish success. message id = %d\n", message_id);
    }

    return rc;
}

static void publishComplete(void* context, unsigned int msgId)
{
    printf("publish message is arrived,id = %d\n",msgId);
}

static void subAckTimeout(SUBSCRIBE_INFO_S *subInfo)
{
    printf("msgId = %d,sub ack is timeout\n",subInfo->msgId);
}

static void *aliyun_iot_pub_thread(void *param)
{
    char buf[MSG_LEN_MAX] = {0};
    static int num = 0;
    int ret = 0;
    Client *client = (Client*)param;
    opendev_socket_t *psocket = &opendev_socket;
    fd_set rfds;
    struct timeval tv;
    int readretry = 0;

    printf("start pub thread\n");
    while(1)
    {
        if (psocket->isready)
        {
            /* receive message from local socket */
            memset(buf, 0, MSG_LEN_MAX);
            FD_ZERO(&rfds);
            FD_SET(psocket->cli_sockfd, &rfds);
            tv.tv_sec = 1;
            tv.tv_usec = 0;

            ret = select(psocket->cli_sockfd + 1, &rfds, NULL, NULL, &tv);
            if (-1 == ret)
            {
                perror("select fail!. INFO"); 
            }
            else if (0 == ret)
            {
                /* select timeout */
            }
            else
            {
                if (FD_ISSET(psocket->cli_sockfd, &rfds))
                {
                    num = read(psocket->cli_sockfd, buf, sizeof(buf));
                    printf("Message from opendev, size: %d\n", num);
                    
                    if (num > 0)
                    {
                        printf("Message from opendev, size: %d. repeat to aliyun...\n", num);
                        if (messageSend(client, buf) != SUCCESS_RETURN)
                        {
                            perror("send message to aliyun iot fail!. INFO");
                        }

                        readretry = 0;
                    }
                    else
                    {
                        if (readretry++ >= 3) // read continue retry time: 3, then reset isready flag of the socket
                        {
                            psocket->isready = 0;
                            readretry = 0;
                        }
                    }
                }
            }
        }
        else
        {
            sleep(1);
        }
    }

    return NULL;
}

static void *aliyun_iot_rpc_pthread(void *param)
{
    int ret;
    socklen_t len;
    opendev_socket_t *psocket = &opendev_socket;

retry:
    memset(psocket, 0, sizeof(psocket));

    psocket->srv_sockfd = socket(PF_UNIX, SOCK_STREAM, 0);
    if(psocket->srv_sockfd < 0)
    {
        perror("cannot create communication socket. INFO");
    }  

    //set server addr_param
    psocket->srv_addr.sun_family = AF_UNIX;
    strncpy(psocket->srv_addr.sun_path, UNIX_DOMAIN, sizeof(psocket->srv_addr.sun_path) - 1);
    unlink(UNIX_DOMAIN);

    //bind sockfd & addr
    ret = bind(psocket->srv_sockfd,(struct sockaddr*)&psocket->srv_addr, sizeof(psocket->srv_addr));
    if(ret == -1)
    {
        perror("cannot bind server socket. INFO");

        goto fail;
    }
    //listen sockfd 
    ret = listen(psocket->srv_sockfd, 1);
    if(ret == -1)
    {
        perror("cannot listen the client connect request. INFO");

        goto fail;
    }

    //have connect request use accept
    while (1)
    {
        len = sizeof(psocket->cli_addr);
        psocket->cli_sockfd = accept(psocket->srv_sockfd, (struct sockaddr *)&psocket->cli_addr, &len);
        if(psocket->cli_sockfd < 0)
        {
            if (EINTR == errno)
            {
                psocket->isready = 0;
                continue;
            }

            perror("cannot accept client connect request. INFO");
            goto fail;
        }
        psocket->isready = 1;
    }

fail:
    printf("local socket init fail! wait 5 seconds to retry\n");  

    psocket->isready = 0;
    close(psocket->cli_sockfd);
    close(psocket->srv_sockfd);
    unlink(UNIX_DOMAIN);
    sleep(5);

    goto retry;

    return NULL;
}

static int aliyun_iot_app_init(unsigned char *msg_buf,unsigned char *msg_readbuf)
{
    int rc = 0;
    pthread_t pthread_pub_id = 0;
    pthread_attr_t attr_pub;
    pthread_t pthread_rpc_id = 0;
    pthread_attr_t attr_rpc;

    memset(msg_buf,0x0,MSG_LEN_MAX);
    memset(msg_readbuf,0x0,MSG_LEN_MAX);

    //初始化设备参数
    IOT_DEVICEINFO_SHADOW_S deviceInfo;
    memset(&deviceInfo, 0x0, sizeof(deviceInfo));

    //设备信息初始化
    deviceInfo.productKey = aliyun_iot_auth_param.product_key; // PRODUCT_KEY;
    deviceInfo.productSecret = aliyun_iot_auth_param.product_secret; //PRODUCT_SECRET;
    deviceInfo.deviceName = aliyun_iot_auth_param.device_name; //DEVICE_NAME;
    deviceInfo.deviceSecret = aliyun_iot_auth_param.device_secret; //DEVICE_SECRET;
    deviceInfo.hostName = HOST_NAME;
    if (0 != aliyun_iot_set_device_info(&deviceInfo))
    {
        printf("run aliyun_iot_set_device_info() error!\n");
        return -1;
    }

    ALIYUN_IOT_USER_INFO_S userInfo;
    memset(&userInfo, 0x0, sizeof(ALIYUN_IOT_USER_INFO_S));

    char *trustPointer = NULL;
    if (SUCCESS_RETURN != aliyun_iot_auth(HMAC_MD5_SIGN_TYPE, IOT_VALUE_FALSE,&userInfo,&trustPointer))
    {
        printf("run aliyun_iot_auth() error!\n");
        return -1;
    }

    Client client;
    memset(&client,0x0,sizeof(client));
    IOT_CLIENT_INIT_PARAMS initParams;
    memset(&initParams,0x0,sizeof(initParams));

    initParams.mqttCommandTimeout_ms = 2000;
    initParams.pHostAdress = userInfo.hostAddress;
    initParams.pHostPort = userInfo.port;
    initParams.pPubKey = trustPointer;
    initParams.pReadBuf = msg_readbuf;
    initParams.readBufSize = MSG_LEN_MAX;
    initParams.pWriteBuf = msg_buf;
    initParams.writeBufSize = MSG_LEN_MAX;
    initParams.protocalType = IOT_NETWORK_MQTT_TLS;
    initParams.disconnectHandler = NULL;
    initParams.disconnectHandlerData = (void*) &client;
    initParams.deliveryCompleteFun = publishComplete;
    initParams.subAckTimeOutFun = subAckTimeout;
    rc = aliyun_iot_mqtt_init(&client, &initParams);
    if (0 != rc)
    {
        printf("ali_iot_mqtt_init failed ret = %d\n", rc);
        return rc;
    }

    MQTTPacket_connectData connectParam;
    memset(&connectParam,0x0,sizeof(connectParam));
    connectParam.cleansession = 1;
    connectParam.clientID.cstring = userInfo.clientId;
    connectParam.username.cstring = userInfo.userName;
    connectParam.password.cstring = NULL;
    connectParam.MQTTVersion = 4;
    connectParam.keepAliveInterval = 60;
    connectParam.willFlag = 0;
    rc = aliyun_iot_mqtt_connect(&client, &connectParam);
    if (0 != rc)
    {
        printf("ali_iot_mqtt_connect failed ret = %d\n", rc);
        return rc;
    }

    rc = aliyun_iot_mqtt_subscribe(&client, aliyun_iot_auth_param.topic1, QOS1, messageArrived);
    if (0 != rc)
    {
        printf("ali_iot_mqtt_subscribe TOPIC1 failed ret = %d\n", rc);
        return rc;
    }

    do
    {
        usleep(10000);
        rc = aliyun_iot_mqtt_suback_sync(&client, aliyun_iot_auth_param.topic1, messageArrived);
    }while(rc != SUCCESS_RETURN);

    rc = aliyun_iot_mqtt_subscribe(&client, aliyun_iot_auth_param.topic2, QOS1, messageArrived);
    if (0 != rc)
    {
        printf("ali_iot_mqtt_subscribe TOPIC2 failed ret = %d\n", rc);
        return rc;
    }

    do
    {
        usleep(10000);
        rc = aliyun_iot_mqtt_suback_sync(&client, aliyun_iot_auth_param.topic2, messageArrived);
    }while(rc != SUCCESS_RETURN);


    /* init local socket */
pthread_fail:
    pthread_attr_init(&attr_pub);
    pthread_attr_setdetachstate(&attr_pub, PTHREAD_CREATE_DETACHED);
    pthread_create(&pthread_pub_id, &attr_pub, aliyun_iot_pub_thread,&client);

    pthread_attr_init(&attr_rpc);
    pthread_attr_setdetachstate(&attr_rpc, PTHREAD_CREATE_DETACHED);
    pthread_create(&pthread_rpc_id, &attr_rpc, aliyun_iot_rpc_pthread,&client);

    while(1)
    {
        if (pthread_pub_id <= 0)
        {
            goto pthread_fail;
        }

        sleep(10);
    }

    pthread_cancel(pthread_pub_id);
    pthread_cancel(pthread_rpc_id);

    aliyun_iot_mqtt_release(&client);

    usleep(100000);

    return 0;
}

static int aliyun_iot_http_msg_parse(void)
{
    static char data[HTTP_RESULT_MAX_SIZE + 1];
    int ret;
    cJSON *root, *jmsg, *jdata;
    int retcode;

    memset(data, 0, HTTP_RESULT_MAX_SIZE);
    ret = file_read(HTTP_WRITEDATA_FILENAME, data, HTTP_RESULT_MAX_SIZE);
    if (ret)
    {
        printf("aliyun_iot_http_msg_parse: read file fail!\n");
    }
    else
    {
        /* json proc */
        root = cJSON_Parse(data);
        if (root && cJSON_GetObjectItem(root, "code"))
        { 
            retcode = cJSON_GetObjectItem(root, "code")->valueint; 
            jmsg = cJSON_GetObjectItem(root, "msg");
            if (jmsg)
            {
                printf("code: %d, msg: %s\n", retcode, jmsg->valuestring);
            }

            switch (retcode)
            {
                case 1:
                {
                    jdata = cJSON_GetObjectItem(root, "data");
                    if (jdata)
                    {
                        strcpy(aliyun_iot_auth_param.product_key,
                                cJSON_GetObjectItem(jdata, "productKey")->valuestring);

                        strcpy(aliyun_iot_auth_param.product_secret,
                                cJSON_GetObjectItem(jdata, "productSecret")->valuestring);

                        strcpy(aliyun_iot_auth_param.device_name,
                                cJSON_GetObjectItem(jdata, "deviceName")->valuestring);

                        strcpy(aliyun_iot_auth_param.device_secret,
                                cJSON_GetObjectItem(jdata, "devicesecret")->valuestring);

                        strcpy(aliyun_iot_auth_param.topic1,
                                cJSON_GetObjectItem(jdata, "topic1")->valuestring);

                        strcpy(aliyun_iot_auth_param.topic2,
                                cJSON_GetObjectItem(jdata, "topic2")->valuestring);

                        if (pshm->isready)
                        {
                            strncpy(pshm->opendev_data.parkingNo, cJSON_GetObjectItem(jdata, "parkingNo")->valuestring,
                                    sizeof(pshm->opendev_data.parkingNo - 1));

                            strncpy(pshm->opendev_data.deviceType, cJSON_GetObjectItem(jdata, "deviceType")->valuestring,
                                    sizeof(pshm->opendev_data.deviceType - 1));

                            pshm->opendev_data.isready = 1;
                        }
                    }

                    unlink(HTTP_WRITEDATA_FILENAME);
                    break;
                }
                case -1:
                {
                    break;
                }
                default:
                    printf("unknown error!\n");
                    break;
            }

            cJSON_Delete(root);

            return 0;
        }
    }

    return -1;
}

static size_t aliyun_iot_http_writedata(void *ptr, size_t size, size_t nmemb, void *userdata)
{
    FILE *fptr = (FILE *)userdata;

    fwrite(ptr, size, nmemb, fptr);
    printf("aliyun_iot_http_writedata write len: %d\n", size * nmemb);

    return (size * nmemb);
}

static int aliyun_iot_http_post(const char *url, const char *postdata)
{
    CURL *curl;
    CURLcode res;
    FILE *fptr;

    while ((fptr = fopen(HTTP_WRITEDATA_FILENAME, "w")) == NULL)
    {
        perror("aliyun_iot_http_post: open file fail! wait 5 seconds to retry: ");
        sleep(5);
    }

retry:
    curl = curl_easy_init();
    if (curl)
    {
        curl_easy_setopt(curl, CURLOPT_URL, url); //url地址  
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata); //post参数  
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, aliyun_iot_http_writedata); //对返回的数据进行操作的函数地址
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, fptr); //这是write_data的第四个参数值  
        curl_easy_setopt(curl, CURLOPT_POST, 1); //设置问非0表示本次操作为post  
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); //打印调试信息  

        res = curl_easy_perform(curl);

        if (res != CURLE_OK)  
        {  
            switch(res)  
            {  
                case CURLE_UNSUPPORTED_PROTOCOL:  
                    printf("Don't support the protocal!\n");  
                    break;
                case CURLE_COULDNT_CONNECT:  
                    printf("Don't connect to remote host or agent!\n");  
                    break;
                case CURLE_HTTP_RETURNED_ERROR:  
                    printf("http return error!\n");  
                    break;
                case CURLE_READ_ERROR:  
                    printf("read the local file fail!\n");  
                    break;
                default:  
                    printf("return code: %d\n",res);  
                    break;
            }  
            curl_easy_cleanup(curl);

            printf("curl_easy_perform fail! wait 5 seconds to retry\n");
            sleep(5);
            goto retry;
        }
    }
    else
    {
        printf("curl_easy_init fail! wait 5 seconds to retry\n");
        sleep(5);
        goto retry;
    }

    curl_easy_cleanup(curl);
    fclose(fptr);

    /* http return msg processing */
    if (aliyun_iot_http_msg_parse())
    {
        while (1)
        {
            printf("Get authorization information fail or the device have been registered!\n");
            sleep(10);
        }
    }

    return 0;
}

static int aliyun_iot_auth_param_get(void)
{
    unsigned long ms;
    char appsign[200] = {0};
    char postdata[200] = {0};
    char signature[SHA1_DIGEST_SIZE];
    char noncestr[7];

    curl_global_init(CURL_GLOBAL_ALL);

    // share memery data init
    while ((pshm = shm_data_init()) == NULL)
    {
        printf("shm_data_init error ! wait 5 seconds to retry\n");
        sleep(5);
    }

    while (!(pshm->isready && SHARE_DATA_MAGIC == pshm->magic))
    {
        printf("share memery data is not ready! wait 5 seconds\n");
        sleep(5);
    }
    printf("serialno[0]: %08x, [1]: %08x\n", pshm->serialno[0], pshm->serialno[1]);

    // calc appsign
    ms = time(NULL);

    // gen random
    genRandomString(noncestr, 6);

    sprintf(appsign, "appkey=%s&appsecret=%s&deviceid=%08x%08x&noncestr=%s&timestamp=%ld",
            HTTP_AUTH_APPKEY,
            HTTP_AUTH_APPSECRET,
            pshm->serialno[0], pshm->serialno[1],
            noncestr, //"q84g8d"
            ms //1459498227
        );
    sha1((unsigned char *)signature, (unsigned char *)appsign, strlen(appsign));

    sprintf(postdata, "deviceid=%08x%08x&appkey=%s&noncestr=%s&timestamp=%ld&signature=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
            pshm->serialno[0], pshm->serialno[1],
            HTTP_AUTH_APPKEY,
            noncestr, //"q84g8d"
            ms, //1459498227
            signature[0], signature[1], signature[2], signature[3], signature[4],
            signature[5], signature[6], signature[7], signature[8], signature[9],
            signature[10], signature[11], signature[12], signature[13], signature[14],
            signature[15], signature[16], signature[17], signature[18], signature[19]);

    printf("appsign str: %s, postdata: %s\n", appsign, postdata);

    /* need to retry when the function failed */
    aliyun_iot_http_post(HTTP_AUTH_URL, postdata);

    return 0;
}

int main()
{
    printf("start demo!\n");

    unsigned char *msg_buf = (unsigned char *)malloc(MSG_LEN_MAX);
    unsigned char *msg_readbuf = (unsigned char *)malloc(MSG_LEN_MAX);

    //get aliyun auth params
    aliyun_iot_auth_param_get();

    //sdk初始化,初始化日志等
retry:
    while (0 != aliyun_iot_auth_init())
    {
        printf("aliyun_iot_auth_init error ! wait 5 seconds to retry\n");
        sleep(5);
    }
    printf("aliyun_iot_auth_init success...\n");

    if (0 != aliyun_iot_app_init(msg_buf,msg_readbuf))
    {
        aliyun_iot_auth_release();
        printf("aliyun_iot_app_init error ! wait 5 seconds to retry\n");
        sleep(5);
        goto retry;
    }

    free(msg_buf);
    free(msg_readbuf);

    aliyun_iot_auth_release();

    printf("exit!\n");

    return 0;
}
